
#include "mod_graphviz.h"

#include <unixd.h>
#include <apr_hash.h>
#include <apr_strings.h>
#define CORE_PRIVATE
#include <http_core.h>
#include <http_protocol.h>
#include <http_log.h>
#include <mod_core.h>

AP_DECLARE_DATA extern module core_module;

static void *graphviz_create_dir_conf(apr_pool_t * p, char *dir)
{
    graphviz_dir_conf *conf = apr_pcalloc(p, sizeof(graphviz_dir_conf));

    conf->default_engine = NULL;
    conf->default_format = NULL;

    return conf;
}

static void *graphviz_merge_dir_config(apr_pool_t * p, void *base_conf,
                                       void *new_conf)
{
    graphviz_dir_conf *base = base_conf;
    graphviz_dir_conf *new = new_conf;

    graphviz_dir_conf *conf = apr_palloc(p, sizeof(*conf));
    conf->default_engine =
        new->default_engine ? new->default_engine : base->default_engine;
    conf->default_format =
        new->default_format ? new->default_format : base->default_format;

    return conf;
}

static void *graphviz_create_srv_conf(apr_pool_t * p, server_rec * c)
{
    graphviz_srv_conf *conf = apr_pcalloc(p, sizeof(graphviz_srv_conf));

    conf->enabled = FALSE;

    conf->engines = apr_hash_make(p);
    conf->formats = apr_hash_make(p);
    conf->font_paths = apr_array_make(p, 5, sizeof(char *));

    return conf;
}

static void *graphviz_merge_srv_conf(apr_pool_t * p, void *base_conf,
                                     void *new_conf)
{
    graphviz_srv_conf *base = base_conf;
    graphviz_srv_conf *new = new_conf;
    graphviz_srv_conf *conf = apr_palloc(p, sizeof(*conf));

    conf->enabled = new->enabled;
    conf->engines = apr_hash_overlay(p, base->engines, new->engines);
    conf->formats = apr_hash_overlay(p, base->formats, new->formats);
    conf->font_paths = apr_array_append(p, base->font_paths, new->font_paths);

    return conf;
}

static const char *graphviz_set_enabled(cmd_parms * parms, void *mconfig,
                                        int on)
{
    graphviz_srv_conf *conf =
        ap_get_module_config(parms->server->module_config, &graphviz_module);

    conf->enabled = on;

    return NULL;
}

static const char *graphviz_add_layout_engine(cmd_parms * parms,
                                              void *mconfig, const char *name,
                                              const char *path)
{
    graphviz_srv_conf *conf =
        ap_get_module_config(parms->server->module_config, &graphviz_module);

    apr_hash_set(conf->engines, name, APR_HASH_KEY_STRING, path);

    return NULL;
}

static const char *graphviz_add_output_format(cmd_parms * parms,
                                              void *mconfig, const char *name,
                                              const char *mime)
{
    graphviz_srv_conf *conf =
        ap_get_module_config(parms->server->module_config, &graphviz_module);

    apr_hash_set(conf->formats, name, APR_HASH_KEY_STRING, mime);

    return NULL;
}

const char *graphviz_add_font_directory(cmd_parms * parms, void *mconfig,
                                        const char *dir)
{
    graphviz_srv_conf *conf =
        ap_get_module_config(parms->server->module_config, &graphviz_module);

    const char **new_dir = apr_array_push(conf->font_paths);
    *new_dir = dir;

    return NULL;
}

const char *graphviz_set_default_layout_engine(cmd_parms * parms,
                                               void *mconfig,
                                               const char *engine)
{
    graphviz_dir_conf *conf = mconfig;

    conf->default_engine = engine;

    return NULL;
}

const char *graphviz_set_default_output_format(cmd_parms * parms,
                                               void *mconfig,
                                               const char *format)
{
    graphviz_dir_conf *conf = mconfig;

    conf->default_format = format;

    return NULL;
}

static const command_rec graphviz_cmds[] = {
    AP_INIT_FLAG("GraphvizSetEnabled", graphviz_set_enabled, NULL, RSRC_CONF,
                 "Enable graphviz on this virtual server"),
    AP_INIT_TAKE2("GraphvizAddLayoutEngine", graphviz_add_layout_engine, NULL,
                  RSRC_CONF, "Add a graphviz layout engine"),
    AP_INIT_TAKE2("GraphvizAddOutputFormat", graphviz_add_output_format, NULL,
                  RSRC_CONF, "Add a graphviz output format"),
    AP_INIT_TAKE1("GraphvizAddFontDirectory", graphviz_add_font_directory,
                  NULL, RSRC_CONF, "Add a font directory"),
    AP_INIT_TAKE1("GraphvizSetDefaultLayoutEngine",
                  graphviz_set_default_layout_engine, NULL, OR_LIMIT,
                  "Set default layout engine for a directory"),
    AP_INIT_TAKE1("GraphvizSetDefaultOutputFormat",
                  graphviz_set_default_output_format, NULL, OR_LIMIT,
                  "Set default output format for a directory"),

    {NULL}
};

static apr_hash_t *parse_arguments(apr_pool_t * p, char *args)
{
    char *current, *postion, *next;
    apr_hash_t *args_hash;

    args_hash = apr_hash_make(p);

    if (args == NULL) {
        return args_hash;
    }

    current = next = apr_pstrdup(p, args);

    while ((next = strchr(current, '&')) != NULL) {
        *next++ = '\0';
        if ((postion = strchr(current, '=')) == NULL) {
            ap_log_perror(APLOG_MARK, APLOG_ERR, APR_EGENERAL, p,
                          "Unregcogized argument string: %s", current);
            continue;
        }
        *postion++ = '\0';
        apr_hash_set(args_hash, current, APR_HASH_KEY_STRING, postion);
        current = next;
    }
    if ((postion = strchr(current, '=')) == NULL) {
        ap_log_perror(APLOG_MARK, APLOG_ERR, APR_EGENERAL, p,
                      "Unregcogized argument string: %s", current);
        return args_hash;
    }
    *postion++ = '\0';
    apr_hash_set(args_hash, current, APR_HASH_KEY_STRING, postion);

    return args_hash;
}

static int graphviz_handler(request_rec * r)
{
    apr_proc_t engine_proc;
    apr_procattr_t *proc_attr;
    apr_hash_t *args_hash;
    const char *engine;
    const char *format;
    const char *args[4];
    const char *env[1];
    apr_bucket *b;
    apr_bucket_brigade *bb;
    apr_status_t status;

    graphviz_srv_conf *sconf =
        ap_get_module_config(r->server->module_config, &graphviz_module);

    if (!sconf->enabled
        || (strcmp(r->handler, GRAPHVIZ_MAGICE_TYPE)
            && strcmp(r->handler, "dot-file"))
        || r->finfo.filetype == 0) {
        return DECLINED;
    }

    if (r->method_number == M_GET) {
        args_hash = parse_arguments(r->pool, r->args);
    }
    else {
        return DECLINED;
    }

    engine = apr_hash_get(args_hash, "engine", APR_HASH_KEY_STRING);
    format = apr_hash_get(args_hash, "format", APR_HASH_KEY_STRING);

    graphviz_dir_conf *dconf =
        ap_get_module_config(r->per_dir_config, &graphviz_module);

    if (engine == NULL) {
        engine = dconf->default_engine;
    }
    if (format == NULL) {
        format = dconf->default_format;
    }
    if (engine == NULL || format == NULL
        || apr_hash_get(sconf->engines, engine, APR_HASH_KEY_STRING) == NULL
        || apr_hash_get(sconf->formats, format,
                        APR_HASH_KEY_STRING) == NULL) {
        return DECLINED;
    }

    apr_procattr_create(&proc_attr, r->pool);
    apr_procattr_cmdtype_set(proc_attr, APR_PROGRAM);

    core_dir_config *cconf =
        ap_get_module_config(r->per_dir_config, &core_module);

#if defined (RLIMIT_DATA) || defined (RLIMIT_VMEM) || defined (RLIMIT_AS)
    apr_procattr_limit_set(proc_attr, APR_LIMIT_MEM, cconf->limit_mem);
#endif

#ifdef RLIMIT_CPU
    apr_procattr_limit_set(proc_attr, APR_LIMIT_CPU, cconf->limit_cpu);
#endif

    apr_procattr_io_set(proc_attr, 0, APR_FULL_BLOCK, APR_FULL_BLOCK);

    args[0] = apr_hash_get(sconf->engines, engine, APR_HASH_KEY_STRING);
    args[1] = apr_pstrcat(r->pool, "-T", format, NULL);
    args[2] = r->filename;
    args[3] = NULL;

    env[0] =
        apr_pstrcat(r->pool, "DOTFONTPATH=",
                    apr_array_pstrcat(r->pool, sconf->font_paths, ':'), NULL);

    if ((status =
         apr_proc_create(&engine_proc, args[0], args, env, proc_attr,
                         r->pool)) != APR_SUCCESS) {
        ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r,
                      "Can't create layout engine process: %s", engine);
        return HTTP_INTERNAL_SERVER_ERROR;
    }

    r->content_type =
        apr_hash_get(sconf->formats, format, APR_HASH_KEY_STRING);

    bb = apr_brigade_create(r->pool, r->connection->bucket_alloc);

    b = apr_bucket_pipe_create(engine_proc.out, bb->bucket_alloc);
    APR_BRIGADE_INSERT_TAIL(bb, b);

    b = apr_bucket_eos_create(bb->bucket_alloc);
    APR_BRIGADE_INSERT_TAIL(bb, b);

    ap_pass_brigade(r->output_filters, bb);

    return OK;
}

static void graphviz_register_hooks(apr_pool_t * p)
{
    ap_hook_handler(graphviz_handler, NULL, NULL, APR_HOOK_MIDDLE);
}

module AP_MODULE_DECLARE_DATA graphviz_module = {
    STANDARD20_MODULE_STUFF,
    graphviz_create_dir_conf,
    graphviz_merge_dir_config,
    graphviz_create_srv_conf,
    graphviz_merge_srv_conf,
    graphviz_cmds,
    graphviz_register_hooks
};
